#!/usr/bin/env python
# -*- coding: utf-8 -*-
import asyncio
import getopt
import locale
import os
import sys
import traceback
from asyncio.events import get_event_loop
from os.path import dirname, basename, exists

import wx
from loguru import logger
from lxml import etree
from uvicorn import Config, Server
from wx.lib.agw.advancedsplash import AdvancedSplash, AS_NOTIMEOUT, AS_CENTER_ON_SCREEN
from wxasync import WxAsyncApp, StartCoroutine

from APPVersion import app_version_master, appchannel
from Beremiz import BeremizIDE
from Beremiz.ProjectController import ProjectController
from Beremiz.util import paths
from Beremiz.util.BitmapLibrary import AddBitmapFolder
from Beremiz.util.TranslationCatalogs import AddCatalog
from backend import store
from backend.gqlClient import wsClient, localClient
from backend.local_port import localPort
from bvpac.oem import oem
from globalobj import application_path, globalOBJ
from resources import GetBitmap

_ = wx.GetTranslation
if __package__ is None:
    sys.path.insert(0, '..')
    __package__ = basename(dirname(sys.argv[0]))


class BeremizIDELauncher(object):
    def __init__(self):
        self.title = oem.title
        self.app = None
        self.frame = None
        self.updateinfo_url = None
        self.extensions = []
        self.app_dir = paths.AbsDir(__file__)
        self.projectOpen = None
        self.buildpath = None
        self.splash = None
        self.splashPath = os.path.join(application_path, "images", oem.logo)
        self.modules = {"BeremizIDE": BeremizIDE}
        self.debug = os.path.exists("BEREMIZ_DEBUG")
        self.handle_exception = None
        self.logf = None
        self.test = False
        AddBitmapFolder(os.path.join(application_path, "images"))
        AddBitmapFolder(os.path.join(application_path, "Beremiz", "images"))
        logger.debug("AddBitmapFolder")
        store.kvpac = self
        self.buildpath = None
        self.current = 'unknown'
        self.tags = []
        self.server = None

    def Bpath(self, *args):
        return os.path.join(application_path, *args)

    def Usage(self):
        print("Usage:")
        print("%s [Options] [Projectpath] [Buildpath]" % sys.argv[0])
        print("")
        print("Supported options:")
        print("-h --help                    Print this help")
        print("-u --updatecheck URL         Retrieve update information by checking URL")
        print("-e --extend PathToExtension  Extend IDE functionality by loading at start additional extensions")
        print("-l --log path                write content of console tab to given file")
        print("")
        print("")

    def SetCmdOptions(self):
        self.shortCmdOpts = "htu:e:"
        self.longCmdOpts = ["help", "test", "updatecheck=", "extend="]

    def ProcessOption(self, o, a):
        if o in ("-h", "--help"):
            self.Usage()
            sys.exit()
        if o in ("-u", "--updatecheck"):
            self.updateinfo_url = a
        if o in ("-e", "--extend"):
            self.extensions.append(a)
        if o in ("-t", "--test"):
            self.test = True
        if o in ("-l", "--log"):
            self.logf = open(a, 'a')

    def ProcessCommandLineArgs(self):
        self.SetCmdOptions()
        try:
            opts, args = getopt.getopt(sys.argv[1:], self.shortCmdOpts, self.longCmdOpts)
        except getopt.GetoptError:
            # print help information and exit:
            self.Usage()
            sys.exit(2)

        for o, a in opts:
            self.ProcessOption(o, a)

        if len(args) > 2:
            self.Usage()
            sys.exit()

        elif len(args) == 1:
            self.projectOpen = args[0]
            self.buildpath = None
        elif len(args) == 2:
            self.projectOpen = args[0]
            self.buildpath = args[1]

    def CreateApplication(self):
        class BeremizApp(WxAsyncApp):
            def OnInit(_self):  # pylint: disable=no-self-argument
                self.ShowSplashScreen()
                return True

        self.app = BeremizApp(redirect=self.debug)
        self.app.SetAppName(self.title)

    def ShowSplashScreen(self):
        class Splash(AdvancedSplash):
            Painted = False

            def OnPaint(_self, event):  # pylint: disable=no-self-argument
                AdvancedSplash.OnPaint(_self, event)
                if not _self.Painted:  # trigger app start only once
                    _self.Painted = True
                    wx.CallAfter(self.AppStart)

        bmp = wx.Image(self.splashPath).ConvertToBitmap()
        self.splash = Splash(None, bitmap=bmp, agwStyle=AS_NOTIMEOUT | AS_CENTER_ON_SCREEN)

    def BackgroundInitialization(self):
        self.InitI18n()
        if appchannel != "alpha":
            self.CheckUpdates()
        self.LoadExtensions()
        self.ImportModules()

    def InitI18n(self):
        from util.misc import InstallLocalRessources
        InstallLocalRessources(self.app_dir)

    def globals(self):
        """
        allows customizations to specify what globals
        are passed to extensions
        """
        return globals()

    def LoadExtensions(self):

        for extfilename in self.extensions:
            from .util.TranslationCatalogs import AddCatalog
            from .util.BitmapLibrary import AddBitmapFolder
            extension_folder = os.path.split(os.path.realpath(extfilename))[0]
            sys.path.append(extension_folder)
            AddCatalog(os.path.join(extension_folder, "locale"))
            AddBitmapFolder(os.path.join(extension_folder, "images"))
            exec(open(extfilename).read())

    def CheckUpdates(self):
        if self.updateinfo_url is not None:
            self.updateinfo = _("Fetching %s") % self.updateinfo_url

            def updateinfoproc():
                try:
                    import urllib2
                    self.updateinfo = urllib2.urlopen(self.updateinfo_url, None).read()
                except Exception:
                    self.updateinfo = _("update info unavailable.")

            from threading import Thread
            self.splash.SetText(text=self.updateinfo)
            updateinfoThread = Thread(target=updateinfoproc, name='updateinfoproc')
            updateinfoThread.start()
            updateinfoThread.join(2)
            self.splash.SetText(text=self.updateinfo)

    def ImportModules(self):
        for modname in self.modules:
            mod = self.modules[modname]
            setattr(self, modname, mod)

    def InstallExceptionHandler(self):
        from .version import app_version
        from .util import ExceptionHandler
        self.handle_exception = ExceptionHandler.AddExceptHook(app_version, logf=self.logf)

    def CreateUI(self):
        self.frame = self.BeremizIDE.Beremiz(None, self.projectOpen, self.buildpath, logf=self.logf)
        self.app.SetTopWindow(self.frame)
        self.frame.setVers(self.current, self.tags)

    def CloseSplash(self):
        if self.splash:
            self.splash.Close()

    def ShowUI(self):
        self.frame.Show()

    def PreStart(self):
        self.ProcessCommandLineArgs()
        self.CreateApplication()

    def AppStart(self):
        logger.debug("AppStart")
        from gqlServer import gqlServer
        try:
            config = Config(gqlServer, host="127.0.0.1", port=localPort.port, log_level="info")
            logger.debug("Server")
            self.server = Server(config=config)
            asyncio.get_event_loop().create_task(self.server.serve())
            logger.debug("local_client begin")
            localClient.begin(localPort.port)
            wsClient.begin(localPort.port)
            logger.debug("BackgroundInitialization")
            self.BackgroundInitialization()
            self.CreateUI()
            self.CloseSplash()
            self.ShowUI()
            if self.test:
                StartCoroutine(self.frame.test, self.frame)
                self.app.ExitMainLoop()
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception as ex:
            logger.error(traceback.format_exc())
            if self.handle_exception is not None:
                self.handle_exception(*sys.exc_info(), exit=True)
            else:
                raise
        if self.buildpath:
            StartCoroutine(self.open_build, self.frame)

    def MainLoop(self):
        import sys
        if '--inspect' in sys.argv:
            import wx.lib.inspection
            wx.lib.inspection.InspectionTool().Show()
        loop = get_event_loop()
        loop.run_until_complete(self.app.MainLoop())

    def Start(self):
        logger.debug("start")
        self.PreStart()
        logger.debug("MainLoop")
        self.InstallExceptionHandler()
        self.MainLoop()

    def open_build(self, buildpath, model):
        try:
            logger.debug("open_build")
            self.ImportModules()
            sys.stdout = sys.__stdout__
            sys.stderr = sys.__stderr__
            logger.debug("open_build")
            Controler = ProjectController()
            Controler.SetAppFrame(None, logger)
            result, err = Controler.LoadProject(buildpath)
            logger.error(err)
            if not result:
                # if self.model:
                #     self.frame.CTR.set_board(self.model)
                loop = asyncio.get_event_loop()
                if loop.run_until_complete(Controler._coBuild(model)):
                    sys.exit(0)
            sys.exit(1)
        except Exception:
            logger.error(traceback.format_exc())

    def __del__(self):
        if self.server:
            loop = asyncio.get_event_loop()
            loop.run_until_complete(self.server.shutdown())

    @property
    def recent_projects(self):
        if not self.frame: return []
        from Beremiz.IDEFrame import DecodeFileSystemPath
        try:
            recent_projects = list(map(DecodeFileSystemPath,
                                       self.frame.GetConfigEntry("RecentProjects", [])))
            recent_projects = [{"path": x} for x in recent_projects]
        except Exception:
            print(traceback.format_exc())
            recent_projects = []
        return recent_projects

    @property
    def project(self):
        if self.frame and self.frame.CTR:
            return self.frame.CTR.project
        return None

    @property
    def current_rte(self):
        if self.frame and self.frame.CTR:
            return self.frame.CTR.current_rte
        return None
    @property
    def demos(self):
        demos = []
        demo_path = os.path.join(os.getcwd(), 'demo')
        if exists(demo_path):
            demo_dirs = os.listdir(demo_path)
            for d in demo_dirs:
                try:
                    if not os.path.isdir(os.path.join(demo_path, d)): continue
                    plc_xml = os.path.join(demo_path, d, 'plc.xml')
                    if not exists(plc_xml): continue
                    xml = etree.parse(plc_xml)
                    project = xml.getroot()
                    ns = '{%s}' % (project.nsmap.get('ppx') or project.nsmap.get(None))
                    pat = './{0}fileHeader'.format(ns)
                    fileHeader = project.find(pat)
                    productVersion = fileHeader.get("productVersion")
                    description = fileHeader.get("contentDescription")
                    contentHeader = project.find('./{0}contentHeader'.format(ns))
                    name = contentHeader.get("name")
                    demos.append(
                        dict(title=name, productVersion=productVersion, path=os.path.join(demo_path, d),
                             description=description))
                except Exception as ex:
                    logger.debug(traceback.format_exc())
        return demos

    todo = []

    @property
    def todos(self):
        return self.todo

    ##@ addtogroup entry
    def open_project(self, path):
        """
        Args:
            path:
        """
        self.frame.OpenProject(path)
        return {"code": 0, "status": "Successed"}

    def CreateApplication(self):
        class BeremizApp(WxAsyncApp):
            def OnInit(_self):  # pylint: disable=no-self-argument
                locale.setlocale(locale.LC_ALL, '')
                logger.debug("ShowSplashScreen")
                self.ShowSplashScreen()
                logger.debug("ShowSplashScreen")
                return True

        logger.debug("CreateApplication")
        self.app = BeremizApp()
        logger.debug("BeremizApp")
        self.app.SetAppName(self.title)

    def InitI18n(self):
        AddBitmapFolder(os.path.join(application_path, 'Beremiz', "images"))

        # Internationalization
        logger.debug("before AddCatalog")
        AddCatalog(os.path.join(application_path, 'Beremiz', "locale"))
        logger.debug("after AddCatalog")

        # locale.setlocale(locale.LC_ALL, 'C')
        # locale.setlocale(locale.LC_CTYPE, 'chinese')

    def CheckUpdates(self):
        loop = asyncio.get_event_loop()
        ver = loop.run_until_complete(localClient.execute_async('''
            query{
            version{
            tags{
            tag
            date
            msg
            changelist
            }
            current
            }
            }
            '''))
        if ver and ver.get('data') and ver.get('data').get('version'):
            self.current = f"{app_version_master}.{ver['data']['version']['current']}"
            self.tags = ver['data']['version']['tags']
            if self.splash:
                self.splash.SetText(text=self.current)
            globalOBJ.appVersion = self.current

    def ShowSplashScreen(self):
        class Splash(AdvancedSplash):
            Painted = False

            def OnPaint(_self, event):  # pylint: disable=no-self-argument
                AdvancedSplash.OnPaint(_self, event)
                logger.debug("OnPaint")
                if not _self.Painted:  # trigger app start only once
                    _self.Painted = True
                    logger.debug("wx.CallAfter(self.AppStart)")
                    wx.CallAfter(self.AppStart)

        logger.debug("ShowSplashScreen")
        bmp = GetBitmap(oem.logo.split('.')[0])
        self.splash = Splash(None, bitmap=bmp, agwStyle=AS_NOTIMEOUT | AS_CENTER_ON_SCREEN)


if __name__ == '__main__':
    beremiz = BeremizIDELauncher()
    beremiz.Start()
